home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The X-Philes (2nd Revision)
/
The X-Philes Number 1 (1995).iso
/
xphiles
/
hp48_2
/
chip.ass
< prev
next >
Wrap
Text File
|
1995-03-23
|
16KB
|
378 lines
Article 2648 of comp.sys.handhelds:
Path: en.ecn.purdue.edu!noose.ecn.purdue.edu!samsung!zaphod.mps.ohio-state.edu!sol.ctr.columbia.edu!ira.uka.de!smurf!wrkof!dis.incom.de!disys!gilles
From: gilles@disys.dis.incom.de (Gilles Kohl)
Newsgroups: comp.sys.handhelds
Subject: A CHIP8 Assembler for the HP48SX [long]
Keywords: CHIP8,Assembler,Native
Message-ID: <1990Nov30.105032.19153@disys.dis.incom.de>
Date: 30 Nov 90 10:50:32 GMT
Organization: Delta Information Systems
Lines: 364
Summary:
--------
This posting describes (and contains) an assembler for Andreas Gustafsons
CHIP-48. The assembler is for the HP48 itself - no computer required. A
successful assembly yields a string that may directly be fed to the CHIP
interpreter.
Introduction:
-------------
The method used has its advantages and has its flaws. Let me list the flaws
first:
- uncompatible with existing sources: I have seen several forms of CHIP-8
sources posted here, including Z80-style (syzygy, puzzle15) implemented via
M80 macros, and one more BASIC-like (pong). The approach being described here
is RPN-style and certainly looks weird at first sight.
- requires getting used to: If you've already written CHIP8 using another
assembler, AS48 will require some accustomization due to its unusual syntax
(and mnemonics - but these can be adapted to your gusto)
- very rudimentary error handling.
Lets have a look at the advantages:
- relatively fast assembling - considering that there is no machine
language involved.
- assembler is relatively small.
- short turn-around: no downloading required.
Nice to learn CHIP8 and to test out routines.
- macros, conditional assembly, modules (and more) are possible
- easily modifiable/expandable
This sounds like a lot for a tiny assembler, and of course there is a trick
to it. The basic idea is not to write a program that would take a text string
and assemble it, but rather have the source assemble _itself_ :)
Example
-------
A source for AS48 is in fact simply an HP48 user program. Every CHIP-8
mnemonic corresponds to a small subroutine which expects its arguments (the
operands for the CHIP-8 instruction, if any) on the stack, and generates the
corresponding opcode (The CHIP-8 string is built on the stack). This is the
reason for the RPN-style syntax. Instead of long explanations, lets have a
look at a source in this format:
@ --- cut here ---
%%HP: T(3)A(D)F(.);
\<< @ User program delimiters
begin @ Start AS48 source
'start' lbl @ Define label 'start' (quotes mandatory)
v0 # FFh movc @ move const FFh into register v0
v0 tset @ set timer with v0
'loop' lbl @ define label 'loop'
charbuf iset @ i := charbuf (no quotes required here)
v0 tget @ get current timer value into v0
v0 sbcd @ store value in v0 as bcd bytes (where i points)
v2 rreg @ read registers (via i) up to v2
v4 # 0h movc @ zero v4
1 2 START @* repeat following twice (at assembly-time!)
0 2 FOR I @* loop from 0 to 2 (at assembly-time!)
v3 I #5 * movc @* v3 := 5 * I (i.e. 0, 5, 10)
v0 I + ichr @* point i to character in vI (tricky bit :-)
v3 v4 # 5h drw @* draw sprite at i on coords (v3,v4)
NEXT @* inner loop
NEXT @* outer loop
v0 #0 seqc @ skip next instruction if v0 == #0
loop jmp @ not timed out yet, continue
start jmp @ timed out, restart
'charbuf' lbl @ character buffer
# 0h db @ define a byte
# 0h db @ and another one
# 0h db @ and the last one
end @ end AS48 source
\>>
@ --- cut again ---
To assemble this source, you would:
- enter it resp. download it to your HP48
- name it (store in a variable)
- give AS48 the quoted variable name as input.
(You may also give AS48 a program as input, but it would be lost after
assembly - just as pressing EVAL with a program in level 1 does)
Most of the 'features' listed above directly result from the fact that the
source is a program itself. You can of course use the built-in editor to
enter your sources, take advantage of the function keys as typing aid, have
various sources stored under different names, enter binary, octal or hex
constants ... macros, conditional assembly, and the like result from
this, too. Lets have a closer look:
- You'll notice several pseudo-ops, one of them is "lbl" which defines a label.
The corresponding name must precede (of course) and be quoted.
References to labels (i.e. " loop jmp " for ex.) do not require the quotes,
but may have them.
- To avoid nameing conflicts, the AS48 mnemonics / pseudo-ops use lower
case. Otherwise, problems with OR, AND, XOR, END ... would have
resulted.
- Macros, conditional assembly and the like simply take advantage of the fact
that RPL is available anyway. In the section above marked '*' in the
comments, the outer loop causes all contained code to be generated twice.
The inner loop expands three times, and generates slightly different code
each time. One must keep in mind that such constructs are evaluated and
executed at assembly-time, not at CHIP8 runtime. The outer loop above would
be more elegantly done using a subroutine. (But its still a nice example)
- Registers are named 'v0' to 'vf'. Each register in fact simply yields a
binary value corresponding to its number. One _may_ take advantage of this,
as done above in the line marked "tricky bit" ...
Mnemonics table
---------------
This is basically the opcode table by Andreas Gustafson, I've just added the
AS48 mnemonics and parameters in the left columns.
Note that:
- The 'call 1802 machine code at nnn' is not available as a mnemonic
- there's more mnemonics than the other assemblers have. I have chosen to
keep AS48 as simple as possible, and not to have an "ld" mnemonic with 7
different flavors. If you don't like the mnemonics, just change them. If you
want the same mnemonic for several different opcodes, you'll have to
differentiate according to parameter types, and maybe introduce a bit of your
own syntax. I'm afraid this would (further) slow the beast down. (And spoil
the basically very simple idea behind all this a bit)
--- cut here for a quickref chart ---
Parameters Mnem. Opcode Description
---------- ----- ------ ---------------------------------------------
c N.A. 0NNN Call 1802 machine code subroutine at NNN
x y mov 8XY0 VX := VY, VF may change
x c movc 6XKK VX := KK
adr iset ANNN I := NNN
x iinc FX1E I := I + VX
x ichr FX29 Point I to 5-byte font sprite for hex character VX
x tset FX15 delay_timer := VX
x tget FX07 VX := delay_timer
x sset FX18 sound_timer := VX
x kget FX0A wait for keypress, store hex value of key in VX
x sreg FX55 Store V0..VX in memory starting at M(I)
x rreg FX65 Read V0..VX from memory starting at M(I)
x sbcd FX33 Store BCD representation of VX in M(I)..M(I+2)
x c addc 7XKK VX := VX + KK
x y add 8XY4 VX := VX + VY, VF := carry
x y sub 8XY5 VX := VX - VY, VF := not borrow
x y subn 8XY7 VX := VY - VX, VF := not borrow
x shl 8XYE VX := VX shl 1, VF := carry
x shr 8XY6 VX := VX shr 1, VF := carry
x y and 8XY2 VX := VX and VY, VF may change
x y or 8XY1 VX := VX or VY, VF may change
x y xor 8XY3 VX := VX xor VY, VF may change
x c rnd CXKK VX := pseudorandom_number and KK
adr jmp 1NNN Jump to NNN
adr jsr 2NNN Call subroutine at NNN
rts 00EE Return from subroutine
x c seqc 3XKK Skip next instruction if VX == KK
x c snec 4XKK Skip next instruction if VX != KK
x y seq 5XY0 Skip next instruction if VX == VY
x y sne 9XY0 Skip next instruction if VX != VY
x skp EX9E Skip next instruction if key VX pressed
x snkp EXA1 Skip next instruction if key VX not pressed
adr jv0 BNNN Jump to NNN+V0
cls 00E0 Clear display
x y c drw DXYN Show N-byte sprite from M(I) at coords (VX,VY), VF := collision
x and y are registers (Use either vn or #n), c is a binary constant,
and adr an (unquoted) label.
Pseudo Ops:
-----------
The following pseudo-ops are available:
Parameter Pseudo-op Description
--------- --------- -----------
begin Start an AS48 source
end End an AS48 source
quoted-name lbl Define a label.
binary int dw deposit a word
binary int db deposit a byte
label aevl evaluate an address.
--- cut again ---
The last one (aevl) requires a bit of explanation. It is required when you're
wanting to do address calculations. Suppose, for example, that you've got:
'table' lbl @ define label 'table'
#48h db
#50h db
#34h db
#38h db @ define some bytes
and want to set register i to the third byte (table+2)
and want to do this calculation at assembly-time. You'd use:
table aevl #2d + iset
The 'aevl' is required here for the forward referencing mechanism to work
correctly. All mnemonics having an address as parameter (jmp, jsr, iset ...)
do this automatically, so it is not required there (that is, if only a label
precedes them). Another example:
Compute the difference between labels 'table' and 'endtable', and store into
variable v0:
v0 endtable aevl table aevl - movc
Note that two aevl's are required here.
Listing
-------
@ --- cut here ---
%%HP: T(3)A(D)F(.);
DIR
MNEM DIR
@ register transfer
movc \<< # 6000h xc \>>
mov \<< # 8000h xy \>>
iset \<< # A000h adr \>>
iinc \<< # F01Eh x \>>
ichr \<< # F029h x \>>
tset \<< # F015h x \>>
tget \<< # F007h x \>>
sset \<< # F018h x \>>
kget \<< # F00Ah x \>>
sreg \<< # F055h x \>>
rreg \<< # F065h x \>>
sbcd \<< # F033h x \>>
@ arithmetic
addc \<< # 7000h xc \>>
add \<< # 8004h xy \>>
sub \<< # 8005h xy \>>
subn \<< # 8007h xy \>>
shl \<< # 800Eh x \>>
shr \<< # 8006h x \>>
and \<< # 8002h xy \>>
or \<< # 8001h xy \>>
xor \<< # 8003h xy \>>
rnd \<< # C000h xc \>>
@ flow control
jmp \<< # 1000h adr \>>
jsr \<< # 2000h adr \>>
rts \<< # 00EEh dw \>>
seqc \<< # 3000h xc \>>
snec \<< # 4000h xc \>>
seq \<< # 5000h xy \>>
sne \<< # 9000h xy \>>
skp \<< # E09Eh x \>>
sknp \<< # E0A1h x \>>
jv0 \<< # B000h adr \>>
@ miscellaneous
cls \<< # 00E0h dw \>>
drw \<< # D000h xyc \>>
@ pseudo ops
lbl \<< PC SWAP STO \>>
db \<< IF 2 FS? THEN B\->R CHR + ELSE DROP END
1 'PC' STO+ PC 3 DISP \>>
dw \<< IF 2 FS? THEN B\->R DUP 256 / IP CHR SWAP 256 MOD CHR + +
ELSE DROP END 2 'PC' STO+ PC 3 DISP \>>
aevl \<< IF 2 FS? THEN EVAL DUP TYPE 6 IF SAME THEN 'UNDEFS' STO+
1 SF # 0h END ELSE DROP #0h END \>>
SYM DIR @ Directory where assembly actually takes place
END @ the 'symbol table' will be created here.
END
NEW \<< \<< begin end \>> \>>
ASM \<< RCWS RCLF \-> ws flg \<< DUP
"AS48 (G. Kohl)\010Initializing ..." 1 DISP
MNEM SYM CLVAR 16 STWS HEX
#0h v0 STO #1h v1 STO #2h v2 STO #3h v3 STO
#4h v4 STO #5h v5 STO #6h v6 STO #7h v7 STO
#8h v8 STO #9h v9 STO #Ah va STO #Bh vb STO
#Ch vc STO #Dh vd STO #Eh ve STO #Fh vf STO
2 CF "PASS 1" 2 DISP EVAL
2 SF {} 'UNDEFS' STO "PASS 2" 2 DISP EVAL
IF 1 FS? THEN UNDEFS "Undefd" \->TAG END
UPDIR UPDIR ws STWS flg STOF \>> \>>
begin \<< 1 CF #200h 'PC' STO IF 2 FS? THEN "" END \>>
end \<< IF 1 FS? THEN DROP END \>>
xyc \<< SWAP OR SWAP #10h * OR SWAP SLB OR dw \>>
x \<< SWAP SLB OR dw \>>
xy \<< SWAP #10h * OR SWAP SLB OR dw \>>
xc \<< OR SWAP SLB OR dw \>>
adr \<< SWAP aevl OR dw \>>
END
@ --- cut again ---
Usage
-----
Download the above directory as AS48 to your HP48. (Use ascii
transfer, translation code 3). Enter AS48. Hitting NEW will yield an empty
AS48 source (Just \<< begin end \>>). You may now enter the MNEM subdirectory
- the AS48 mnemonics are available as typing aids at this level. Use
[gold][EDIT] to modify the source. When done editing, leave the MNEM
subdirectory ([gold][UP]) and store your source under a name of your choice.
To assemble, enter this name (quoted) and hit ASM. (As an AS48 source is
really an RPL program, you may assemble sources located above ASM in the
directory tree. ASM relies upon being called just above MNEM, though)
Successfull assembly will yield a CHIP8 string in level 1. If undefined
symbols exist, a list containing them will instead be returned. There is very
few error checking (to keep this thing reasonably fast), so be careful.
Assembling your source may suddenly stop with an RPL error message if
something's wrong (bad parameter types, for example). If the problem cause
isn't obvious, remember that an AS48 source is really an RPL program: it can
be debugged ...
Tips & Tricks, Expansions
-------------------------
Symbol table: After an assembly, changing to MNEM SYM (and evtl. hitting VAR)
will yield the 'symbol table'. During assembly, every label creates a
variable of that name, the contents of the variable being of course the
CHIP8-adress where it was created. Predefined variables are v0 .. vf (the
CHIP8 registers) and PC. After a successful assembly, PC points to the last
byte (+1) of the generated code, in CHIP8 address space.
Macros: Macros may be implemented by creating an RPL program at the start of
your source, naming it, and calling it (with appropriate parameters) in the
rest of the source. Hint: flag 2 is internally used by ASM to keep track if
its on pass 1 or 2. You'll need to create your macro on pass 1 only, so you
might include it into an IF 2 FC? THEN ... END sequence. The macro itself may
also take advantage of flag 2.
Modules: a module is but an AS48 source with the 'begin' 'end' brackets
removed. You can then call it from another source. Such modules may reside
higher in the directory tree than ASM.
Computed tables: tables of various sorts can be computed using RPL (and some
dw's or db's). An interesting extension might be an RPL program to turn a
GROB into a sprite, or even to directly turn a character (string) into
CHIP8-sprites.
Conversion to a more standard form: The following device could be used to
automatically convert an AS48 source into a format suitable for other
assemblers: in a separate directory, create a small program for every AS48
mnemonic (and pseudo-op) and make it output its own name (in converted form)
as well as its parameters, in prefix form. (Maybe output directly to the
serial port) Then, converting a source (and uploading to a computer) is just a
matter of changing to that directory and evaluating the source.
----
Hope AS48 can be useful for somebody. This is my first attempt at both
posting in this forum and at writing anything non-trivial in RPL. Please
be indulgent, excuse my mistakes, but tell me about them :-)